package cn.mju.tankfighterbackend.service.game

import cn.mju.tankfighterbackend.dto.game.GameLog
import cn.mju.tankfighterbackend.dto.game.PlayerOptionDto
import cn.mju.tankfighterbackend.dto.game.SyncFrameDto
import cn.mju.tankfighterbackend.entity.game.Game
import com.easy.server.persistenceCollection.JacksonSerializer
import com.easy.server.persistenceCollection.fileBasedImpl.FilePersistenceArrayList
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicBoolean

fun getGameFrameFilePathById(gameId: Long): String {
    return "./gamefiles/$gameId/game.fme"
}

fun getGameLogFilePathById(gameId: Long): String {
    return "./gamefiles/$gameId/game.log"
}

fun getGameFileDir(gameId: Long): String {
    return "./gamefiles/$gameId"
}

class GameExecutor(val game: Game) {

    init {
        val gameFileDir = Path.of(getGameFileDir(game.id!!))
        if (!Files.exists(gameFileDir)) {
            Files.createDirectories(gameFileDir)
        }
    }

    private val syncFrames: FilePersistenceArrayList<SyncFrameDto> =
        FilePersistenceArrayList(
            getGameFrameFilePathById(game.id!!),
            JacksonSerializer(SyncFrameDto::class.java),
            3000
        )

    private val gameLogs: FilePersistenceArrayList<GameLog> =
        FilePersistenceArrayList(
            getGameLogFilePathById(game.id!!),
            JacksonSerializer(GameLog::class.java),
            1000
        )

    /**
     * 当前帧中各个玩家的操作
     * Int -> playerId
     */
    private var currentSyncFramePlayerOptions = HashMap<Int, PlayerOptionDto>()


    private val frameSyncChannel = Channel<SyncFrameDto>()

    private val scope = CoroutineScope(Dispatchers.Default)

    private val mutex = Mutex()


    var started: AtomicBoolean = AtomicBoolean(false)

    /**
     * 用户id与playerId的映射 在游戏申请成功后就要分配
     */
    val playerIdMapper: MutableMap<String, Int> = HashMap()


    val playerIdAllocator = arrayOfNulls<String>(game.maxPlayerCount)

    /**
     * 在线玩家
     */
    val onlineUsers: MutableSet<String> = HashSet()

    @Volatile
    var gameIsOver: Boolean = false

    private fun getIndexInPersistenceList(syncFrameIndex: Int): Int {
        return (syncFrameIndex / 3)
    }


    private fun log(ctx: String) {
        gameLogs.add(GameLog(LocalDateTime.now(), ctx))
    }

    /**
     * 同步一个玩家的帧
     */
    fun sync(syncFrameIndex: Int, option: PlayerOptionDto, userName: String) {
        if (gameIsOver) {
            return
        }
        scope.launch {
            mutex.withLock {
                //由服务端设置玩家id 而不信任客户端 以防作弊
                option.playerId = playerIdMapper[userName]!!
                if (gameIsOver) {
                    log("游戏已结束 丢弃同步帧 帧号: ${syncFrameIndex} playerId:${option.playerId}")
                }
                if (game.lastFrame != syncFrameIndex) {
                    log("非预期的同步帧: 预期:${game.lastFrame} 但是接收了$syncFrameIndex , playerId:${option.playerId}")

                    return@launch
                }
                currentSyncFramePlayerOptions[option.playerId] = option
                if (currentSyncFramePlayerOptions.size == game.maxPlayerCount) {

                    val options = currentSyncFramePlayerOptions.toMap()
                    val syncFrameDto = SyncFrameDto(syncFrameIndex,
                        options,
                        game.id!!,
                        options.maxOf { it.value.timeStamp }
                    )
                    syncFrames.add(syncFrameDto)
                    game.lastFrame += game.syncFrameInterval
                    currentSyncFramePlayerOptions.clear()

                    if (isGameOver(syncFrameDto)) {
                        gameIsOver = true
                    }
                    frameSyncChannel.send(syncFrameDto)
                }
            }
        }
    }

    private fun isGameOver(syncFrameDto: SyncFrameDto): Boolean {
        var over = true
        syncFrameDto.options.forEach { over = over and it.value.gameOver }
        return over
    }

    @Synchronized
    fun tryStartGame(onNextSyncFrame: (SyncFrameDto) -> Unit) {
        if (!started.get()) {
            started.set(true)

            scope.launch {
                while (true) {
                    val nextSyncFrame = nextSyncFrame()
                    onNextSyncFrame(nextSyncFrame)
                }
            }
        }

    }

    private suspend fun nextSyncFrame(): SyncFrameDto {
        return frameSyncChannel.receive()
    }

    /**
     * 主动请求一个帧
     */
    fun requestFrame(frameIndices: Array<Int>, onNextSyncFrame: (Array<SyncFrameDto>) -> Unit) {
        scope.launch {
            mutex.withLock {
                val frames = mutableListOf<SyncFrameDto>()
                frameIndices.take(9999).forEach { frameIndex->
                    val indexInPersistenceList = getIndexInPersistenceList(frameIndex)
                    if (syncFrames.size > indexInPersistenceList) {
                        val syncFrameDto = syncFrames[indexInPersistenceList]
                        frames.add(syncFrameDto)
                    }
                }
                onNextSyncFrame(frames.toTypedArray())
            }
        }
    }

    @Synchronized
    fun isPlayerInGame(userName: String): Boolean {
        return playerIdMapper.containsKey(userName)
    }

    @Synchronized
    fun getPlayerId(userName: String): Int {
        return playerIdMapper[userName]!!
    }

    /**
     * 一个玩家连接进游戏，暂时没有其他作用
     */
    @Synchronized
    fun playerConnect(userName: String) {
        onlineUsers.add(userName)
    }

    /**
     * 一个玩家断开连接，暂时没有其他作用
     */
    @Synchronized
    fun playerDisconnect(userName: String) {
        onlineUsers.remove(userName)
    }

    /**
     * 尝试加入游戏 分配一个玩家id
     * @return 玩家id -1 表示加入失败 房间已满或者游戏已启动
     */
    @Synchronized
    fun tryJoinGame(userName: String): Int {

        if (started.get()) {
            return -1
        }

        acquirePlayerId(userName).let { playerId ->
            if (playerId != -1) {
                playerIdMapper[userName] = playerId
                return playerId
            }
        }
        return -1
    }

    /**
     * 退出游戏 可以在游戏中途主动退出
     * @return 如果玩家不在这个游戏中 返回false 不然会退出游戏 返回true
     */
    @Synchronized
    fun exitGame(userName: String): Boolean {

        if (isPlayerInGame(userName)) {
            releasePlayerId(userName)
            return true
        }

        return false
    }

    /**
     * 申请分配一个玩家id 用于加入房间
     * @return -1 如果没有可分配的playerId 房间已满
     */
    @Synchronized
    private fun acquirePlayerId(userName: String): Int {
        for (index in playerIdAllocator.indices) {
            val s = playerIdAllocator[index]
            if (s == null) {
                playerIdAllocator[index] = userName
                return index + 1
            }
        }

        return -1
    }

    /**
     * 归还一个玩家id 用于退出房间
     */
    @Synchronized
    fun releasePlayerId(userName: String): Boolean {
        playerIdMapper[userName]?.let { id ->
            playerIdAllocator[id] = null
            playerIdMapper.remove(userName)
            return true
        }
        return false
    }


    @Synchronized
    fun getPlayerNamesList(): List<String> {
        return this.playerIdMapper.keys.toList()
    }

    /**
     * 当所有房间中的玩家已经连接到了游戏中 则可以开始游戏
     */
    @Synchronized
    fun canStartGame(): Boolean {
        return housePlayerCount() == onlineUsers.size && onlineUsers.size == game.maxPlayerCount
    }


    /**
     * 该房间中的玩家数量 玩家不一定在线 也可能disconnect了 这种情况下就需要等待玩家重新连接
     */
    fun housePlayerCount(): Int {
        return playerIdMapper.size
    }
}